package com.ztorn.cdc.mysql;

import com.ztorn.cdc.AbstractCDCBuilder;
import com.ztorn.cdc.CDCBuilder;
import com.ztorn.common.assertion.Asserts;
import com.ztorn.common.constant.ClientConstant;
import com.ztorn.common.constant.FlinkParamConstant;
import com.ztorn.common.model.FlinkCDCConfig;
import com.ztorn.schema.MysqlJsonDebeziumDeserializationSchema;
import com.ververica.cdc.connectors.mysql.source.MySqlSource;
import com.ververica.cdc.connectors.mysql.source.MySqlSourceBuilder;
import com.ververica.cdc.connectors.mysql.table.StartupOptions;
import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import java.io.Serializable;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class MysqlCDCBuilder extends AbstractCDCBuilder implements Serializable {

    private static final long serialVersionUID = 120102122355112221L;

    public static final String KEY_WORD = "mysql-cdc";
    public static final String METADATA_TYPE = "MySql";

    public MysqlCDCBuilder(){}

    public MysqlCDCBuilder(FlinkCDCConfig config){
        super(config);
    }

    @Override
    public String getSchema() {
        return config.getSchema();
    }

    @Override
    public String getHandle() {
        return KEY_WORD;
    }

    @Override
    public CDCBuilder create(FlinkCDCConfig config) {
        return new MysqlCDCBuilder(config);
    }

    @Override
    public DataStreamSource<String> build(StreamExecutionEnvironment env) {
        String database = config.getDatabase();
        String serverId = config.getSource().get("server-id");
        String serverTimeZone = config.getSource().get("server-time-zone");
        String fetchSize = config.getSource().get("scan.snapshot.fetch.size");
        String connectTimeout = config.getSource().get("connect.timeout");
        String connectMaxRetries = config.getSource().get("connect.max-retries");
        String connectionPoolSize = config.getSource().get("connection.pool.size");
        String heartbeatInterval = config.getSource().get("heartbeat.interval");
        String chunkSize = config.getSource().get("scan.incremental.snapshot.chunk.size");
        String timestampMillis = config.getSource().get("scan.startup.timestampMillis");

        Properties debeziumProperties = new Properties();
        // 为部分转换添加默认值
        debeziumProperties.setProperty("bigint.unsigned.handling.mode", "long");
        debeziumProperties.setProperty("decimal.handling.mode", "string");
        debeziumProperties.setProperty("converters", "mydebeziumconverter");
        debeziumProperties.setProperty("mydebeziumconverter.type", "com.ztorn.cdc.mysql.DateTimeConverter");
        debeziumProperties.setProperty("mydebeziumconverter.database.type", "mysql");
        // 自定义格式，可选
        debeziumProperties.setProperty("mydebeziumconverter.format.datetime", "yyyy-MM-dd HH:mm:ss");
        debeziumProperties.setProperty("mydebeziumconverter.format.date", "yyyy-MM-dd");
        debeziumProperties.setProperty("mydebeziumconverter.format.time", "HH:mm:ss");

//        for (Map.Entry<String, String> entry : config.getDebezium().entrySet()) {
//            if (Asserts.isNotNullString(entry.getKey())
//                    && Asserts.isNotNullString(entry.getValue())) {
//                debeziumProperties.setProperty(entry.getKey(), entry.getValue());
//            }
//        }

        // 添加jdbc参数注入
        Properties jdbcProperties = new Properties();
//        for (Map.Entry<String, String> entry : config.getJdbc().entrySet()) {
//            if (Asserts.isNotNullString(entry.getKey())
//                    && Asserts.isNotNullString(entry.getValue())) {
//                jdbcProperties.setProperty(entry.getKey(), entry.getValue());
//            }
//        }

        MySqlSourceBuilder<String> sourceBuilder =
                MySqlSource.<String>builder()
                        .hostname(config.getHostname())
                        .port(config.getPort())
                        .username(config.getUsername())
                        .password(config.getPassword());

        if (Asserts.isNotNullString(database)) {
            String[] databases = database.split(FlinkParamConstant.SPLIT);
            sourceBuilder.databaseList(databases);
        } else {
            sourceBuilder.databaseList(new String[0]);
        }

        List<String> schemaTableNameList = config.getSchemaTableNameList();
        if (Asserts.isNotNullCollection(schemaTableNameList)) {
            sourceBuilder.tableList(
                    schemaTableNameList.toArray(new String[schemaTableNameList.size()]));
        } else {
            sourceBuilder.tableList(new String[0]);
        }

        sourceBuilder.deserializer(new JsonDebeziumDeserializationSchema());
        sourceBuilder.debeziumProperties(debeziumProperties);
        sourceBuilder.jdbcProperties(jdbcProperties);

        if (Asserts.isNotNullString(config.getStartupMode())) {
            switch (config.getStartupMode().toLowerCase()) {
                case "initial":
                    sourceBuilder.startupOptions(StartupOptions.initial());
                    break;
                case "latest-offset":
                    sourceBuilder.startupOptions(StartupOptions.latest());
                    break;
                case "earliest-offset":
                    sourceBuilder.startupOptions(StartupOptions.earliest());
                    break;
                case "timestamp":
                    sourceBuilder.startupOptions(
                            StartupOptions.timestamp(
                                    Asserts.isNotNullString(timestampMillis)
                                            ? Long.valueOf(timestampMillis)
                                            : System.currentTimeMillis()));
                    break;
                default:
            }
        } else {
            sourceBuilder.startupOptions(StartupOptions.latest());
        }

        if (Asserts.isNotNullString(serverId)) {
            sourceBuilder.serverId(serverId);
        }

        if (Asserts.isNotNullString(serverTimeZone)) {
            sourceBuilder.serverTimeZone(serverTimeZone);
        }

        if (Asserts.isNotNullString(fetchSize)) {
            sourceBuilder.fetchSize(Integer.valueOf(fetchSize));
        }

        if (Asserts.isNotNullString(connectTimeout)) {
            sourceBuilder.connectTimeout(Duration.ofMillis(Long.valueOf(connectTimeout)));
        }

        if (Asserts.isNotNullString(connectMaxRetries)) {
            sourceBuilder.connectMaxRetries(Integer.valueOf(connectMaxRetries));
        }

        if (Asserts.isNotNullString(connectionPoolSize)) {
            sourceBuilder.connectionPoolSize(Integer.valueOf(connectionPoolSize));
        }

        if (Asserts.isNotNullString(heartbeatInterval)) {
            sourceBuilder.heartbeatInterval(Duration.ofMillis(Long.valueOf(heartbeatInterval)));
        }

        if (Asserts.isAllNotNullString(chunkSize)) {
            sourceBuilder.splitSize(Integer.parseInt(chunkSize));
        }
        if(config.isIncludeSchemaChanges()){
            sourceBuilder.includeSchemaChanges(config.isIncludeSchemaChanges());
        }
        if(config.isScanNewlyAddedTableEnabled()){
            sourceBuilder.scanNewlyAddedTableEnabled(config.isScanNewlyAddedTableEnabled());
        }

        return env.fromSource(
                sourceBuilder.build(), WatermarkStrategy.noWatermarks(), "MySQL CDC Source");
    }

    @Override
    public Map<String, Map<String, String>> parseMetaDataConfigs() {
        Map<String, Map<String, String>> allConfigMap = new HashMap<>();
        List<String> schemaList = getSchemaList();
        for (String schema : schemaList) {
            Map<String, String> configMap = new HashMap<>();
            configMap.put(ClientConstant.METADATA_TYPE, METADATA_TYPE);
            StringBuilder sb = new StringBuilder("jdbc:mysql://");
            sb.append(config.getHostname());
            sb.append(":");
            sb.append(config.getPort());
            sb.append("/");
            sb.append(schema);
            configMap.put(ClientConstant.METADATA_NAME, sb.toString());
            configMap.put(ClientConstant.METADATA_URL, sb.toString());
            configMap.put(ClientConstant.METADATA_USERNAME, config.getUsername());
            configMap.put(ClientConstant.METADATA_PASSWORD, config.getPassword());
            allConfigMap.put(schema, configMap);
        }
        return allConfigMap;
    }

    @Override
    public Map<String, String> parseMetaDataConfig() {
        Map<String, String> configMap = new HashMap<>();
        configMap.put(ClientConstant.METADATA_TYPE, METADATA_TYPE);
        StringBuilder sb = new StringBuilder("jdbc:mysql://");
        sb.append(config.getHostname());
        sb.append(":");
        sb.append(config.getPort());
        sb.append("/");
        configMap.put(ClientConstant.METADATA_NAME, sb.toString());
        configMap.put(ClientConstant.METADATA_URL, sb.toString());
        configMap.put(ClientConstant.METADATA_USERNAME, config.getUsername());
        configMap.put(ClientConstant.METADATA_PASSWORD, config.getPassword());

        return configMap;
    }

    @Override
    public String getSchemaFieldName() {
        return "db";
    }
}
